//******************************************************************
//******************************************************************
//**********          ANts Peer To Peer Sources        *************
//
// ANts P2P realizes a third generation P2P net. It protects your
// privacy while you are connected and makes you not trackable, hiding
// your identity (ip) and crypting everything you are sending/receiving
// from others.

// Copyright (C) 2004  Roberto Rossi

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

package ants.p2p.security.sockets;

import java.net.*;
import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.*;
import java.security.interfaces.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import javax.crypto.interfaces.*;
import java.util.*;

import ants.p2p.*;

import com.sun.crypto.provider.SunJCE;

import org.apache.log4j.*;

public class SecureServerSocketThread{
  public static final String cipher = "AES";
  public static final int cipherKeySize = 16;
  public static final int DHKeyExchangeBits = 512;

  static Logger _logger = Logger.getLogger(SecureServerSocketThread.class.getName());

  DHParameterSpec dhParamSpec;
  KeyPair serverKpair;
  KeyAgreement serverKeyAgree;
  PublicKey serverPubKey;
  PublicKey clientPubKey;

  byte[] sharedSecret;
  byte[] aesKey;
  Cipher serverCipherEnc;
  Cipher serverCipherDec;

  int remoteServerPort;
  Socket localSocket;
  InputStream socketInputStream;
  OutputStream socketOutputStream;
  CipherInputStream cipherSocketInputStream;
  CipherOutputStream cipherSocketOutputStream;

  boolean newVersionDetected = false;
  String newerVersion = Ant.getVersion();

  long timeElapsed;
  int pingSize = 32;

  public SecureServerSocketThread(Socket localSocket) throws IOException,
      NoSuchAlgorithmException,
      InvalidParameterSpecException,
      InvalidAlgorithmParameterException,
      InvalidKeyException {
    this.localSocket = localSocket;
    this.localSocket.setSoTimeout(60000);
    this.run();
    if(!this.localSocket.isClosed())
      this.localSocket.setSoTimeout(3 * 60000);
  }

  private void generateDHParameters()throws IOException,
      NoSuchAlgorithmException,
      InvalidParameterSpecException,
      InvalidAlgorithmParameterException,
      InvalidKeyException {

    AlgorithmParameterGenerator paramGen = AlgorithmParameterGenerator.
        getInstance("DH");
    paramGen.init(SecureServerSocketThread.DHKeyExchangeBits);
    AlgorithmParameters params = paramGen.generateParameters();
    dhParamSpec = (DHParameterSpec) params.getParameterSpec(DHParameterSpec.class);
    //dhParamSpec = new DHParameterSpec(skip1024Modulus, skip1024Base);

    KeyPairGenerator serverKpairGen = KeyPairGenerator.getInstance("DH");
    serverKpairGen.initialize(dhParamSpec);
    serverKpair = serverKpairGen.generateKeyPair();

    serverKeyAgree = KeyAgreement.getInstance("DH");
    serverKeyAgree.init(serverKpair.getPrivate());

    serverPubKey = serverKpair.getPublic();
  }

  public void run(){
    try{
      /*this.generateDHParameters();
      this.serverCipherEnc = Cipher.getInstance(SecureServerSocketThread.cipher);
      this.serverCipherDec = Cipher.getInstance(SecureServerSocketThread.cipher);*/

      this.socketInputStream = localSocket.getInputStream();
      this.socketOutputStream = localSocket.getOutputStream();

      ObjectOutputStream keyAgreementOutStream = new ObjectOutputStream(this.socketOutputStream);
      ObjectInputStream keyAgreementInStream = new ObjectInputStream(this.socketInputStream);

      String protocolVersion = (String)keyAgreementInStream.readObject();
      keyAgreementOutStream.writeObject(Ant.getProtocolVersion());
      if(!protocolVersion.equals(Ant.getProtocolVersion())){
         if (!protocolVersion.startsWith("beta") &&
             protocolVersion.compareTo(Ant.getProtocolVersion()) > 0) {
           this.newVersionDetected = true;
           this.newerVersion = "Protocol update: " + protocolVersion;
         }
         this.localSocket.close();
         return;
      }
      String applicationName = (String)keyAgreementInStream.readObject();
      if(applicationName.equals(Ant.getApplicationName())){
        String version = (String) keyAgreementInStream.readObject();
        if (!version.equals(Ant.getVersion())) {
          if (version.compareTo(Ant.getVersion()) > 0) {
            this.newVersionDetected = true;
            this.newerVersion = version;
          }
        }
      }else{
        keyAgreementInStream.readObject();
      }
      keyAgreementOutStream.writeObject(Ant.getApplicationName());
      keyAgreementOutStream.writeObject(Ant.getVersion());
      this.remoteServerPort = keyAgreementInStream.readInt();

      long startProbe = System.currentTimeMillis();
      keyAgreementInStream.readObject();
      timeElapsed = System.currentTimeMillis() - startProbe;
      keyAgreementOutStream.writeObject(new byte[pingSize]);

      /*keyAgreementOutStream.writeObject(this.dhParamSpec.getG().toString());
      keyAgreementOutStream.writeObject(this.dhParamSpec.getP().toString());
      keyAgreementOutStream.writeObject(((DHPublicKey)serverPubKey).getY().toString());
      keyAgreementOutStream.flush();
      BigInteger y = new BigInteger((String)keyAgreementInStream.readObject());
      DHPublicKeySpec clientPubKeySpec = new DHPublicKeySpec(y, this.dhParamSpec.getP(),this.dhParamSpec.getG());
      this.clientPubKey = KeyFactory.getInstance("DH").generatePublic(clientPubKeySpec);

      KeyFactory serverKeyFac = KeyFactory.getInstance("DH");
      serverKeyAgree.doPhase(clientPubKey, true);

      this.sharedSecret = serverKeyAgree.generateSecret();

      this.aesKey = new byte[SecureServerSocketThread.cipherKeySize];
      for (int x = 0; x < this.aesKey.length; x++) {
        this.aesKey[x] = this.sharedSecret[x];
      }
      SecretKeySpec serverKeySpec = new SecretKeySpec(aesKey, SecureServerSocketThread.cipher);

      this.serverCipherEnc.init(Cipher.ENCRYPT_MODE, serverKeySpec);
      this.serverCipherDec.init(Cipher.DECRYPT_MODE, serverKeySpec);*/

    }catch(Exception e){
      try{
        this.localSocket.close();
      }catch(Exception ex){}
      _logger.info("Secure server connection failed with: "+localSocket.getInetAddress()+":"+localSocket.getPort()+" "+e.getMessage(),e);
      _logger.debug("Secure server connection failed",e);
    }
  }

  public long getTimeElapsed(){
    return this.timeElapsed;
  }

  public Cipher getCipherEnc() {
    return this.serverCipherEnc;
  }

  public Cipher getCipherDec() {
    return this.serverCipherDec;
  }

  public Socket getSocket(){
    return this.localSocket;
  }

  public int getRemoteServerPort(){
    return this.remoteServerPort;
  }

  public boolean isNewVersionDetected(){
    return this.newVersionDetected;
  }

  public String getNewerVersion(){
    return this.newerVersion;
  }
}
